package com.zxjbyte.yiyi.module.plugin.job.config;

import cn.hutool.extra.spring.SpringUtil;
import lombok.RequiredArgsConstructor;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

import javax.sql.DataSource;
import java.util.Properties;

/**
 * quartz 配置
 *
 * @Author zhangxingjia
 * @Date 2023/11/1 19:06
 * @Version: 1.0
 */
@Configuration
public class QuartzBaseConfig {



    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
        SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
        // 设置数据源
        factoryBean.setDataSource(dataSource);
        factoryBean.setAutoStartup(true);
        // 是否等待正在运行的作业在关闭时完成
        factoryBean.setWaitForJobsToCompleteOnShutdown(true);
        // 延时启动
        factoryBean.setStartupDelay(1);
        /**
         * 配置在调度器初始化时，是否覆盖已存在的作业。
         *
         * 设置为true，那么当调度器初始化时，它会覆盖已经存在的同名作业。也就是说，如果调度器中已经存在一个与要添加的作业同名的作业，
         * 那么新添加的作业会覆盖掉已经存在的作业。这样，调度器中就只会存在新添加的作业，而不会保留同名的旧作业。
         */
        factoryBean.setOverwriteExistingJobs(true);
        /**
         * 设置在SchedulerContext中存储Spring应用程序上下文（ApplicationContext）的键值
         *
         * 例如在Quartz作业需要访问Spring容器中的Bean可以使用指定的键值来获取Spring应用程序上下文
          */
        factoryBean.setApplicationContextSchedulerContextKey("applicationContextKey");
        factoryBean.setQuartzProperties(quartzProperties());
        factoryBean.setJobFactory(jobFactory());
        return factoryBean;
    }

    @Bean
    public SpringBeanJobFactory jobFactory() {
        AutowireCapableBeanFactory beanFactory = SpringUtil.getApplicationContext().getAutowireCapableBeanFactory();
        return new AutowiringSpringBeanJobFactory(beanFactory);
    }


    private Properties quartzProperties() {
        // Quartz属性
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "QuartzScheduler");
        prop.put("org.quartz.scheduler.instanceId", "AUTO"); // 自动生成 instance ID
        // JobStore配置
        prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore"); // 这个类是Quartz和Spring集成的一部分，用于将调度作业和触发器信息存储在Spring配置的数据源中
        // 集群配置
        prop.put("org.quartz.jobStore.isClustered", "true"); // 设置为“true”以打开集群功能
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        prop.put("org.quartz.jobStore.misfireThreshold", "60000"); // 用于定义调度器在触发错过的触发器（misfired triggers）之前允许的时间偏差
        prop.put("org.quartz.jobStore.useProperties", "false"); // 默认的存储方式，即以二进制形式存储JobDataMap数据
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
        // 线程池配置
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "25"); // 并行执行job可用的线程数
        prop.put("org.quartz.threadPool.threadPriority", "5");
        prop.put("org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread", "true");
        // jdbc
        prop.put("org.quartz.jdbc.initialize-schema", "NEVER"); // 数据库架构初始化模式, 选择手动创建
        return prop;
    }

    @RequiredArgsConstructor
    public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory {

        private final AutowireCapableBeanFactory beanFactory;

        @Override
        protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
            Object jobInstance = super.createJobInstance(bundle);
            beanFactory.autowireBean(jobInstance);
            beanFactory.initializeBean(jobInstance,  jobInstance.getClass().getName());
            return jobInstance;
        }
    }
}
